#ifndef HEADERS_CLUSTER
#define HEADERS_CLUSTER

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#include "debug.h"

typedef char* symbol;

struct cl_list;
struct job_list;
struct dep_list;
struct ident_list;

typedef int cl_time_t;
typedef enum priority_t {low, medium, high} priority_t;

#define DO_NOT_REGISTER 0
#define DO_REGISTER 1
#define UN_REGISTER 2
#define TIME_ERROR -2
#define TIME_INITIAL -1
#define TIME_SCOPE 0

#define GET_TIME_OF_LAST -1

struct cluster {
    cl_time_t create_time;  // Create time is relative to the surounding scope

    // Note: most of these could be elements of the attributes list. Consider
    // moving these into the single attributes list.

    //! @TODO type should be a cl_list, but I need to have a debugging attribute
    // for now. Once Str_cl is workable, and I have a cl_call(cl, symbol) name

    // TASK: Add a list for invokers. For example, Program_cl invokes DummyPrint_cl, so DummyPrint_cl
    // should have Program_cl on its list of invokers. This way, when cl_invoke exits, DummyPrint_cl can
    // remove itself from its invoker's jobs list and instead place itself on Program_cl's job_memos list.
    symbol name;
    struct ident_list* identity;
    struct cl_list* description;
    struct cl_list* attributes;
    struct cl_list* referenced_by;
    struct cl_list* scope;
    struct cl_list* parents;
    struct cl_list* grammar;
    struct job_list* jobs;
    struct job_list* job_memos;
    struct dep_list* dependencies;
    void* data;  // This is so that the integer cluster has a place to store the relevant data.

    void (*job_handler)(struct cluster*, struct job_list* job);

    // To be called when this cluster is invoked. The parameter is
    // intended to be the self cluster, and the parameters used by
    // the function should be in the dependencies list.
    struct cluster* (*invoke_func)(struct cluster*);
    // To be called on destruction of a cluster. This hook is here
    // in case a cluster needs to allocate dynamic memory (such as
    // a string).
    void (*destroy_func)(struct cluster*);

    pthread_mutex_t* mutex;
    int ref_count;
};

struct cl_list {
    struct cluster* cl;
    struct cl_list* next;
};

struct job_list {
    struct cluster* caller;
    cl_time_t call_time;
    struct job_list* next;
};

typedef struct cluster cluster;
typedef struct cl_list cl_list;
typedef struct job_list job_list;

//! @TODO: Evaluate whether the bool returns are necessary. It might be nice to be able to check
// if a call made it through, but it might also be a waste of space.
cluster* cl_new();
cl_list* cl_list_new();
void cl_init(cluster* cl);
void cl_set_name(cluster* cl, symbol name);
void cl_set_invoke_func(cluster* cl, cluster* (*func)(cluster*));
void cl_set_destroy_func(cluster* cl, void (*func)(cluster*));

void cl_append(cl_list** list_loc, cluster* to_append);
cluster* cl_remove(symbol name, cl_list** list_loc);
void cl_empty_list(cl_list** list_loc);
cluster* cl_read(symbol name, cl_list* list);
cluster* cl_invoke(cluster* cl);
void cl_invoke_destroy(cluster* cl);
cluster* cl_set_create_time(cluster* cl, cl_time_t time);
void cl_mutex_lock(cluster* cl);
void cl_mutex_unlock(cluster* cl);
void cl_remove_job(job_list** job_list_loc, job_list* job);
void cl_add_job(job_list** job_list_loc, job_list* job);

// These are defined in registry.c
void cl_register(cluster* cl);
void cl_unregister(cluster* cl);

// These are defined in dep_list.c
void cl_set_dep(cluster* callee, symbol dep_name, cluster* caller, cl_time_t time, cluster* caller_connect);
cluster* cl_get_dep(cluster* callee, symbol dep_name, cluster* caller, cl_time_t time);
void cl_clear_dep_list(cluster* cl);
void cl_clear_dep_list_by_cluster(cluster* callee, cluster* caller);

// These are defined in identity.c
void cl_set_identity(cluster* cl, cl_time_t time, cluster* value);
cluster* cl_get_identity(struct ident_list* list, cl_time_t time);
void cl_empty_ident_list(struct ident_list** list_loc);

void info_dump(cluster* cl);
void info_dump_list(cl_list* list);
#endif
